/*
 * User: rant
 * Date: 12/12/2006
 *
 *
 */
package laMalinche.testingPlayGround.flow;

import laMalinche.editableContext.AboutToChangeManager;
import laMalinche.editableContext.BasicAboutToChangeManager;
import laMalinche.propertyChangeAdapters.model.OgnlHolder;
import laMalinche.rete.nodes.StopEvaluatingException;
import laMalinche.rete.nodes.editing.CertaintyManager;
import laMalinche.rete.nodes.editing.OpenFieldCertaintyManager;
import laMalinche.rete.nodes.tailing.BasicTailingManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 *
 */
public class FlowHolder implements Flow {
    private final FlowNode root;
    private FlowNode currentNode;
    private final AboutToChangeManager aboutToChangeManager;
    private final BasicTailingManager basicTailingManager;

    public FlowHolder(FlowNode root) {
        this.root = root;
        aboutToChangeManager = new BasicAboutToChangeManager();
        // todo this must be some basic certaintyManager
        final CertaintyManager certaintyManager = new OpenFieldCertaintyManager();
        certaintyManager.setAboutToChangeManager(getAboutToChangeManager());
        basicTailingManager = new BasicTailingManager();
        basicTailingManager.addPropertyTouchedListener(certaintyManager);
    }

    private FlowNode getNextNode() {
        FlowNode nextNode;
        if (currentNode == null) {
            nextNode = root;
        } else {
            nextNode = currentNode.getNextChild();
        }
        return nextNode;
    }

    public boolean hasNextCollector() {
        if (getNextNode() != null) {
            return true;
        } else {
            return false;
        }
    }

    public void updateAboutToChange() {
        FlowNode node = currentNode;
        if (currentNode == null)
            node = root;
        updateAboutToChange(node.getState());
    }

    public CollectorIfc getNextCollector(String transition) {
        if (currentNode == null) {
            currentNode = root;
        } else {
            currentNode = currentNode.getNextChild();
        }
        final BasicAboutToChangeManager basicAboutToChangeManager = ((BasicAboutToChangeManager) aboutToChangeManager);
        final List<String> list = new ArrayList<String>(getAboutToChangeElements());
        basicAboutToChangeManager.setAboutToChange(list);
        basicAboutToChangeManager.adviseListeners();
        final CollectorIfc collector = currentNode.getState();
        basicAboutToChangeManager.setInEditingProcess(new ArrayList<String>(getOgnls(collector.getModel(), new HashSet(collector.getFields()))));
        return currentNode.getState();
    }

    public CollectorIfc getNextCollector() {
        currentNode = getNextNode();
        final CollectorIfc collector = currentNode.getState();
        updateAboutToChange(collector);
        return currentNode.getState();
    }

    private void updateAboutToChange(final CollectorIfc currentColector) {
        final BasicAboutToChangeManager basicAboutToChangeManager = ((BasicAboutToChangeManager) aboutToChangeManager);
        final Set<String> aboutTo = getAboutToChangeElements();
        final List<String> list = new ArrayList<String>();
        if (aboutTo != null) {
            list.addAll(aboutTo);
        }
        basicAboutToChangeManager.setAboutToChange(list);
        basicAboutToChangeManager.adviseListeners();
        basicAboutToChangeManager.setInEditingProcess(new ArrayList<String>(getOgnls(currentColector.getModel(), new HashSet(currentColector.getFields()))));
    }

    public Collector getPreviousCollector() {
        throw new RuntimeException("not implemented");
    }

    public AboutToChangeManager getAboutToChangeManager() {
        return aboutToChangeManager;
    }

    public Set<String> getAboutToChangeElements() {
        FlowNode nextNode = getNextNode();
        return (nextNode != null) ? getOpenFields(nextNode) : null;
    }

    public Set<String> getInEditingProcessElements() {
        return null;
    }

    protected Set<String> getAboutToChangeElements(@Nullable BasicFlowNode nextNode) {
        return (nextNode != null) ? getOpenFields(nextNode) : null;
    }

    private Set<String> getOpenFields(@NotNull FlowNode startNode) {
        Set<String> openFields = new HashSet<String>();
        List<FlowNode> visited = new ArrayList<FlowNode>();
        Stack<FlowNode> stack = new Stack<FlowNode>();
        stack.push(startNode);
        while (!stack.isEmpty()) {
            FlowNode node1 = stack.pop();
            if (!visited.contains(node1)) {
                visited.add(node1);
                openFields.addAll(getFieldsFromNode(node1));
                List<FlowNode> possibleChildren = getPossibleChildren(node1);
                for (FlowNode node : possibleChildren) {
                    if (node != null) {
                        stack.push(node);
                    }
                }
            }
        }
        return openFields;
    }

    /**
     * @return The list of possible flow nodes by exculding immposible nodes based on values that are used by navigation rules and cant change during the remaining flow.
     */
    private List<FlowNode> getPossibleChildren(FlowNode node) {
        List<FlowNode> nodeList = new ArrayList<FlowNode>();
        // todo support stopEvaluation Exception
        basicTailingManager.actStarted();
        FlowNode child = null;
        boolean stopEvaluating = false;
        try {
            child = node.getNextChild();
        } catch (StopEvaluatingException e) {
            nodeList.addAll(node.getPossibleChildren());
            stopEvaluating = true;
        }
        basicTailingManager.actFinished();
        if (!stopEvaluating) {
            nodeList.add(child);
        }
        return nodeList;
    }

    private Set<String> getFieldsFromNode(FlowNode node) {
        CollectorIfc collector = node.getState();
        final Set<String> fields = collector.getFields();
        return getOgnls(collector.getModel(), fields);
    }

    private Set<String> getOgnls(final OgnlHolder model, final Set<String> fields) {
        Set<String> ognls = new HashSet<String>();
        for (String field : fields) {
            String ognl = model.getOgnl();
            if (ognl != "")  {   ognl = ognl + "."; }
            ognls.add(ognl + field);
        }
        return ognls;
    }
}